﻿namespace CollisionDetection.ViewModels
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Windows.Threading;
    using Common.Logging;
    using Extended;
    using IS.Projecto.Modelo;
    using Models;

    public class CollisionDetectorViewModel : INotifyPropertyChanged
    {
        #region Fields

        private ILog _log = LogManager.GetLogger(typeof (CollisionDetectorViewModel));
        private DateTime _endDate;
        private DateTime _initialDate;
        private string _warningText;
        private bool _isAnalyzing;
        private StringBuilder _output;
        private double _progressValue;
        private Dispatcher _currentDispatcher = Dispatcher.CurrentDispatcher;
        private EnumTipoOrigem _tipoOrigemDados;
        private DataService _dataService;
        private IList<Curso> _listCursos;
        private int _selectedWeek;
        private Curso _selectedCurso;
        private string _excelFilename;

        #endregion

        #region Properties

        public DateTime InitialDate
        {
            get { return _initialDate; }
            set
            {
                _initialDate = value;
                InvokePropertyChanged("InitialDate");
            }
        }
        public DateTime EndDate
        {
            get { return _endDate; }
            set
            {
                _endDate = value;
                InvokePropertyChanged("EndDate");
            }
        }
        public string Output { get { return _output.ToString(); } }
        public double ProgressValue
        {
            get { return _progressValue; }
            private set
            {
                _progressValue = value;
                InvokePropertyChanged("ProgressValue");
            }
        }
        public bool IsNotAnalyzing
        {
            get { return !_isAnalyzing; }
        }
        public bool IsAnalyzing
        {
            get { return _isAnalyzing; }
            set
            {
                _isAnalyzing = value;
                InvokePropertyChanged("IsAnalyzing");
                InvokePropertyChanged("IsNotAnalyzing");
            }
        }
        public EnumTipoOrigem TipoOrigemDados
        {
            get { return _tipoOrigemDados; }
            set
            {
                _tipoOrigemDados = value;
                _dataService.TipoOrigemDados = _tipoOrigemDados;
                ListCursos = _dataService.GetCursos();
                InvokePropertyChanged("TipoOrigemDados");
            }
        }
        public string WarningText
        {
            get { return _warningText; }
            private set
            {
                _warningText = value;
                InvokePropertyChanged("WarningText");
            }
        }
        public int SelectedWeek
        {
            get { return _selectedWeek; }
            set
            {
                _selectedWeek = value;
                var days = (_selectedWeek - 1)*7;
                var today = DateTime.Today;
                _initialDate = new DateTime(today.Year, 1, 1).AddDays(days);
                if (today.Month > 8 && _initialDate.Month < 8)
                    _initialDate = _initialDate.AddYears(1);
                else if (today.Month <= 8 && _initialDate.Month >= 8)
                    _initialDate = _initialDate.AddYears(-1);

                InitialDate = _initialDate.AddDays(8.0 - (int)_initialDate.DayOfWeek);
                EndDate = _initialDate.AddDays(5);
                //EndDate = _initialDate;
                InvokePropertyChanged("SelectedWeek");
            }
        }
        public Curso SelectedCurso
        {
            get { return _selectedCurso; }
            set
            {
                _selectedCurso = value;
                InvokePropertyChanged("SelectedCurso");
            }
        }
        public IList<Curso> ListCursos
        {
            get { return _listCursos; }
            private set
            {
                _listCursos = value;
                if (_listCursos != null && _listCursos.Count > 0)
                    SelectedCurso = ListCursos[0];
                InvokePropertyChanged("ListCursos");
            }
        }

        public string ExcelFilename
        {
            get { return _excelFilename; }
            set
            {
                _excelFilename = value;
                InvokePropertyChanged("ExcelFilename");
            }
        }

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Constructors

        public CollisionDetectorViewModel()
        {
            _isAnalyzing = false;
            _output = new StringBuilder();
            if (DesignHelper.IsInDesignModeStatic)
            {
                SelectedWeek = DateTime.Today.DayOfYear / 7;
                _tipoOrigemDados = EnumTipoOrigem.toDatabase;
                ListCursos = new List<Curso> { new Curso { Nome = "Eng. Inf", Regime = "Diurno" } };
                return;
            }
            var settings = new AppSettings();
            _dataService = new DataService();
            SelectedWeek = settings.SelectedWeek;
            TipoOrigemDados = settings.TipoOrigemDados;
            ExcelFilename = settings.ExcelFilename;
        }

        ~CollisionDetectorViewModel()
        {
            var settings = new AppSettings();
            settings.SelectedWeek = SelectedWeek;
            settings.TipoOrigemDados = TipoOrigemDados;
            settings.ExcelFilename = ExcelFilename;
        }

        #endregion

        #region Members

        public void Analize()
        {
            IsAnalyzing = true;
            _output = new StringBuilder("A iniciar a análise..." + Environment.NewLine);

            var work = new BackgroundWorker();
            work.DoWork += CollisionDetectioDoWork;
            work.RunWorkerAsync();
            work.RunWorkerCompleted += (s, e) => { IsAnalyzing = false; };
        }

        public void IncrementWeek()
        {
            SelectedWeek = _selectedWeek < 52 ? _selectedWeek + 1 : 1;
        }

        public void DecrementWeek()
        {
            SelectedWeek = _selectedWeek > 1 ? _selectedWeek - 1 : 52;
        }

        public void ExcelFileDialog()
        {
            var dialog = new SaveFileDialog();
            dialog.FileName = ExcelFilename;
            dialog.AddExtension = true;
            dialog.DefaultExt = "xls";
            dialog.Filter = "Excel document|*.xls;*.xlsx";
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                ExcelFilename = Path.GetFullPath(dialog.FileName);
            }
        }

        protected void InvokePropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
        }

        private void CollisionDetectioDoWork(object sender, DoWorkEventArgs e)
        {
            IDictionary<string, IDictionary<string, IList>> data = new SortedList<string, IDictionary<string, IList>>();
            _progressValue = 0;
            AddOutputLine("Carregar registos de Horários de Unidade Curriculares...");
            var horarios = _dataService.GetHorarios(_selectedCurso, _initialDate, _endDate);

            AddOutputLine("Foram carregados {0} registos de Horários de Unidade Curriculares", horarios.Count);

            AddOutputLine("A preencher grelha de análise...");
            ProgressValue = 0;

            #region Preenche a grelha de colisões
            
            for (int i = 0; i < horarios.Count; i++)
            {
                var h = horarios[i];
                var dataDay = data.ContainsKey(h.DataHora.ToShortDateString()) ? data[h.DataHora.ToShortDateString()] : null;
                if (dataDay == null)
                {
                    dataDay = new SortedList<string, IList>();
                    data.Add(h.DataHora.ToShortDateString(), dataDay);
                }
                var halfs = h.Duracao / 30;
                for (int j = 0; j < halfs; j++)
                {
                    var time = h.DataHora.AddMinutes(j * 30);
                    var key = time.ToShortTimeString();
                    var dataTime = dataDay.ContainsKey(key) ? dataDay[key] : null;
                    if (dataTime == null)
                    {
                        dataTime = new List<Horario>();
                        dataDay.Add(key, dataTime);
                    }

                    dataTime.Add(h);
                    _log.DebugFormat("collision key\t{4}\t{0}\t{1}({2}{3})", h.DataHora, h.UcId, h.TipoAula, h.TurnoId, h.Id);
                }
                ProgressValue = (double)(i * 100) / horarios.Count;
            } 

            #endregion

            AddOutputLine("Analisar colisões de Unidade Curriculares...");
            ProgressValue = 0;

            var collisionDictionary = new Dictionary<string, IList<Collision>>();
            var collisions = new List<Collision>();

            #region Verifica as colisões de Unidades curriculares

            for (int a = 0; a < data.Count; a++)
            {
                var dataDay = data.ToList()[a];
                foreach (var dataTime in dataDay.Value)
                {
                    if (dataTime.Value.Count <= 1) continue;

                    for (int i = 0; i < dataTime.Value.Count - 1; i++)
                    {
                        var h1 = (Horario)dataTime.Value[i];
                        for (int j = i + 1; j < dataTime.Value.Count; j++)
                        {
                            var h2 = (Horario)dataTime.Value[j];
                            if (h1.Equals(h2) || (h1.UcId == h2.UcId && h1.TipoAula == h2.TipoAula)) continue;

                            var key = string.Format("{0}-{1}({2})-{4}({5})", dataDay.Key, h1.UcId, h1.TipoAula, h1.TurnoId, h2.UcId, h2.TipoAula, h2.TurnoId);
                            _log.DebugFormat("key\t{0}", key);
                            IList<Collision> collisionList = collisionDictionary.ContainsKey(key) ? collisionDictionary[key] : null;
                            if (collisionList == null)
                            {
                                collisionList = new List<Collision>();
                                collisionDictionary.Add(key, collisionList);
                            }

                            var c = (from p in collisionList
                                     where p.Horario1.Equals(h1) && p.Horario2.Equals(h2)
                                     select p).FirstOrDefault();

                            if (c == null)
                            {
                                c = new Collision
                                        {
                                            Data = DateTime.Parse(dataDay.Key),
                                            Horario1 = h1,
                                            Horario2 = h2,
                                            Start = TimeSpan.Parse(dataTime.Key),
                                            Duration = 30
                                        };
                                collisionList.Add(c);
                                collisions.Add(c);
                            }
                            else
                            {
                                if (TimeSpan.Parse(dataTime.Key) == c.End)
                                    c.Duration += 30;
                            }
                        }
                    }
                }
                ProgressValue = (double)(a * 100) / data.Count;
            } 

            #endregion

            //for (int i = 0; i < Math.Min(20,collisions.Count); i++)
            //{
            //    AddOutputLine("\t{0}-{1} -> {2}-{3}", collisions[i].Horario1.UcId, collisions[i].Horario2.UcId, collisions[i].Start, collisions[i].End);
            //}
            AddOutputLine("Foram encontradas {0} colisões de Unidades Curriculares.", collisions.Count);
            _progressValue = 0;
            AddOutputLine("Carregar Inscrições...");
            var inscricoes = _dataService.GetInscricoes(_selectedCurso);
            AddOutputLine("Foram carregados {0} Inscrições", inscricoes.Count);

            AddOutputLine("Analisar colisões com alunos...");
            ProgressValue = 0;

            var collisionRelation = new SortedList<string, Collision>();
            var attendance = new List<UcAttendance>();
            for (int i = 0; i < collisions.Count; i++)
            {
                var c = collisions[i];
                Collision c2 = null;
                var key1 = string.Format("{6} -> {0} ({1}) - {3} ({4})", c.Horario1.UcId,
                                         c.Horario1.TipoAula, c.Horario1.TurnoId, c.Horario2.UcId,
                                         c.Horario2.TipoAula, c.Horario2.TurnoId, c.Data + c.Start);
                var key2 = string.Format("{6} -> {0} ({1}) - {3} ({4})", c.Horario2.UcId,
                                         c.Horario2.TipoAula, c.Horario2.TurnoId, c.Horario1.UcId,
                                         c.Horario1.TipoAula, c.Horario1.TurnoId, c.Data + c.Start);

                if (collisionRelation.ContainsKey(key1))
                    c2 = collisionRelation[key1];
                else if (collisionRelation.ContainsKey(key2))
                    c2 = collisionRelation[key2];

                //colisão já foi encontrada e analisada
                if (c2 != null)
                {
                    #region Colisão já analisada

                    //Se for uma colisão normal (diferença está apenas no turno)
                    if ((c2.Horario1.UcId == c.Horario1.UcId && c2.Horario1.TipoAula == c.Horario1.TipoAula)
                        || (c2.Horario1.UcId == c.Horario2.UcId && c2.Horario1.TipoAula == c.Horario2.TipoAula))
                        continue;

                    var warning = String.Format("Ocorreu um conflito:{0}Colisão 1" +
                                                "{0}\t(Horário 1)({1}) {2} ({3} m) {4} ({5}{6}) \tSala:{7} \tDocente:{8}" +
                                                "{0}\t(Horário 2)({9}) {10} ({11} m) {12} ({13}{14}) \tSala:{15} \tDocente:{16}" +
                                                "{0}Colisão 2" +
                                                "{0}\t(Horário 1)({17}) {18} ({19} m) {20} ({21}{22}) \tSala:{23} \tDocente:{24}" +
                                                "{0}\t(Horário 2)({25}) {26} ({27} m) {28} ({29}{30}) \tSala:{31} \tDocente:{32}",
                                                Environment.NewLine, c.Horario1.Id, c.Horario1.DataHora,
                                                c.Horario1.Duracao, c.Horario1.UcId, c.Horario1.TipoAula,
                                                c.Horario1.TurnoId, c.Horario1.SalaId, c.Horario1.DocenteId
                                                , c.Horario2.Id, c.Horario2.DataHora, c.Horario2.Duracao,
                                                c.Horario2.UcId, c.Horario2.TipoAula, c.Horario2.TurnoId,
                                                c.Horario2.SalaId, c.Horario2.DocenteId
                                                , c2.Horario1.Id, c2.Horario1.DataHora, c2.Horario1.Duracao,
                                                c2.Horario1.UcId, c2.Horario1.TipoAula, c2.Horario1.TurnoId,
                                                c2.Horario1.SalaId, c2.Horario1.DocenteId
                                                , c2.Horario2.Id, c2.Horario2.DataHora, c2.Horario2.Duracao,
                                                c2.Horario2.UcId, c2.Horario2.TipoAula, c2.Horario2.TurnoId,
                                                c2.Horario2.SalaId, c2.Horario2.DocenteId);


                    AddOutputLine(warning);
                    _log.WarnFormat(warning);

                    #endregion
                }
                else
                {
                    #region Analisa as colisões

                    //calcula o nº de alunos inscritos a ambas as disciplinas
                    var left = from p in inscricoes
                               where p.UcId == c.Horario1.UcId
                               select p;
                    var right = from p in inscricoes
                                where p.UcId == c.Horario2.UcId
                                select p;
                    c.Collisions = left.Intersect(right, new IncricaoComparer()).Count();

                    #region Calcula o nº inscritos e nº alunos sem colisões ás disciplinas

                    var ath1 = new UcAttendance
                                   {
                                       UcId = c.Horario1.UcId,
                                       TipoAula = c.Horario1.TipoAula,
                                       StudentsSigned = left.Count(),
                                       StudentsWithCollisions = c.Collisions
                                   };
                    var ath2 = new UcAttendance
                    {
                        UcId = c.Horario1.UcId,
                        TipoAula = c.Horario1.TipoAula,
                        StudentsSigned = right.Count(),
                        StudentsWithCollisions = c.Collisions
                    };

                    //é considerado sempre o maior numero de possiveis colisões
                    if (attendance.Contains(ath1))
                    {
                        var a = attendance[attendance.IndexOf(ath1)];
                        a.StudentsWithCollisions = Math.Max(a.StudentsWithCollisions, ath1.StudentsWithCollisions);
                    }
                    else if (ath1.StudentsWithCollisions > 0)
                        attendance.Add(ath1);

                    if (attendance.Contains(ath2))
                    {
                        var a = attendance[attendance.IndexOf(ath2)];
                        a.StudentsWithCollisions = Math.Max(a.StudentsWithCollisions, ath2.StudentsWithCollisions);
                    }
                    else if (ath2.StudentsWithCollisions > 0)
                        attendance.Add(ath2);

                    #endregion


                    //Se houver inscrições às uc em colisão analisa se existe alternativa
                    if (c.Collisions > 0)
                    {
                        //verifica se existe alguma alternativa
                        var cs = (from p in collisions
                                  where (p.Data + p.Start != c.Data + c.Start)
                                        && ((p.Horario1.UcId == c.Horario1.UcId
                                             && p.Horario2.UcId == c.Horario2.UcId
                                             && p.Horario1.TipoAula == c.Horario1.TipoAula
                                             && p.Horario2.TipoAula == c.Horario2.TipoAula) ||
                                            (p.Horario1.UcId == c.Horario2.UcId
                                             && p.Horario2.UcId == c.Horario1.UcId
                                             && p.Horario1.TipoAula == c.Horario2.TipoAula
                                             && p.Horario2.TipoAula == c.Horario1.TipoAula))
                                  select p).ToList();

                        if (cs.Count == 0)
                            collisionRelation.Add(key1, c);
                    } 

                    #endregion
                }

                ProgressValue = (double)(i * 100) / collisions.Count;
            }

            AddOutputLine("Foram encontradas {0} colisões:", collisionRelation.Count);
            foreach (var c in collisionRelation.Select(collision => collision.Value))
            {
                AddOutputLine("\t{0} ({1}) - {3} ({4}) == {6} {7}->{8} ({9})", c.Horario1.UcId,
                              c.Horario1.TipoAula, c.Horario1.TurnoId, c.Horario2.UcId,
                              c.Horario2.TipoAula, c.Horario2.TurnoId,
                              c.Horario1.DataHora.ToShortDateString(), c.Start, c.End,
                              c.Collisions);
            }

            AddOutputLine("Inscrições e possiveis colisões :");
            foreach (var att in attendance)
            {
                AddOutputLine("\t{0} ({1}) - {2} - {3}", att.UcId, att.TipoAula, att.StudentsSigned, att.StudentsWithCollisions);
            }

            new ExcelAutomation().CreateExcelFile(ExcelFilename, collisionRelation, attendance);
        }

        private delegate void AddSafeLineDelegate(string text, params object[] args);

        private void AddOutputLine(string text, params object[] args)
        {
            AddSafeLineDelegate addSafeLine = (t, a) =>
                                                  {
                                                      _output.AppendLine(string.Format(t, a));
                                                      InvokePropertyChanged("Output");
                                                  };
            _currentDispatcher.Invoke(addSafeLine, new object[] {text, args});
        }

        #endregion
    }

    public class IncricaoComparer : IEqualityComparer<Inscricao>
    {
        public bool Equals(Inscricao x, Inscricao y)
        {
            // Check whether the compared objects reference the same data.
            if (ReferenceEquals(x, y)) return true;

            // Check whether any of the compared objects is null.
            if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
                return false;

            return x.EstudanteId == y.EstudanteId;
        }

        public int GetHashCode(Inscricao obj)
        {
            // Check whether the object is null.
            if (ReferenceEquals(obj, null)) return 0;

            // Get the hash code for the Name field if it is not null.
            return obj.EstudanteId.GetHashCode();
        }
    }
}